package com.crocodileq.utils;


import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.annotations.Param;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/***
 * @Author 鳄鱼杆
 * @Date 2025/9/11 19:45
 */

@Component
public class RedisUtils {

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 缓存数据
     * @param key 健
     * @param value 值
     * @param time  时间
     * @param timeUnit 时间单位
     */
    public void setCacheData(String key, Object value, long time, TimeUnit timeUnit){

        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
    }

    /**
     * 缓存数据，并设置逻辑过期时间
     * @param key 健
     * @return 值
     */
    public void setCacheDataWithLogicalExpire(String key, Object value, long time, TimeUnit timeUnit){

        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds( time)));

        stringRedisTemplate.opsForValue().set(key,  JSONUtil.toJsonStr(redisData));
    }

    /**
     * 缓存数据，并设置逻辑过期时间
     * 处理缓存穿透问题
     * @param keyPrefix 健
     * @return 值
     */
    public <R,ID> R getCacheData(
            String keyPrefix, ID id, Class<R> type, long time, TimeUnit timeUnit, Function<ID,R> callback){
        String strJson = stringRedisTemplate.opsForValue().get(keyPrefix + id);
        //缓存是否命中，命中则返回
        if(StrUtil.isNotBlank(strJson)){
            //反序列化
            return JSONUtil.toBean(strJson, type);
        }
        //空值判断
        if(strJson != null){
            //返回报错信息
            return null;
        }
        //缓存未命中，查询数据库
        R db = callback.apply(id);
        //判断数据库是否存在，不存在则返回错误
        if(ObjectUtils.isEmpty(db)){
            //缓存null，并设置过期时间
            stringRedisTemplate.opsForValue().set(keyPrefix + id,
                    "",
                    RedisConstants.CACHE_NULL_TTL,
                    TimeUnit.MINUTES
            );
            //返回报错信息
            return null;
        }
        //缓存信息
        this.setCacheData(keyPrefix + id, db, time, timeUnit);
        return db;

    }


    public <R,ID> R getCacheDataWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, long time, TimeUnit timeUnit, Function<ID,R> callback){

        //查询缓存
        String json = stringRedisTemplate.opsForValue().get(keyPrefix + id);

        //缓存未命中，返回空
        if(StrUtil.isBlank(json)){
            return null;
        }
        //缓存命中,判断缓存是否过期
        RedisData<R> redisData = JSON.parseObject(json, new TypeReference<RedisData<R>>() {});

        LocalDateTime expireTime = redisData.getExpireTime();
        if(expireTime.isAfter(LocalDateTime.now())){
            //未过期，返回数据
            return redisData.getData();
        }

        //获取锁，失败，返回过期数据
        boolean isLock = this.tryLock(keyPrefix + id);
        //获取锁成功
        //开启独立线程，查询数据库，重建缓存
        if(isLock){
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    R r = callback.apply(id);
                    this.setCacheDataWithLogicalExpire(keyPrefix + id, r, time, timeUnit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    unLock(keyPrefix + id);
                }
            });
        }

        return redisData.getData();

    }

    /**
     * 获取锁
     * @param key
     * @return
     */
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);

        return BooleanUtil.isTrue(flag);
    }

    /**
     * 释放锁
     * @param key
     */
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }
}
